﻿//  Copyright (c) 2011 Ray Liang (http://www.dotnetage.com)
//  Dual licensed under the MIT and GPL licenses:
//  http://www.opensource.org/licenses/mit-license.php
//  http://www.gnu.org/licenses/gpl.html

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Web.UI;
using System.Collections;
using System.Reflection;
using System.Web.Mvc;
using System.Linq.Expressions;

namespace DNA.Mvc.jQuery
{
    public class Grid : ContainerViewComponent<GridColumn>
    {
        private List<GridColumnRef> groupColumns;
        private List<GridOrderedColumnRef> orderColumns;
        private HtmlTemplate footer = new HtmlTemplate();
        private HtmlTemplate header = new HtmlTemplate();
        private HtmlTemplate<object> detailView = new HtmlTemplate<object>();

        /// <summary>
        /// Gets/Sets the ClientDetailView object
        /// </summary>
        public ClientDetailView ClientDetailView { get; set; }

        /// <summary>
        /// Gets/Sets whether the Grid is auto generated column for current data model type.
        /// </summary>
        public bool AutoGenerateColumns { get; set; }

        /// <summary>
        /// Gets/Sets whether the grid fixed the width ,height and show scrollbar.
        /// </summary>
        public bool ShowScrollBar { get; set; }

        /// <summary>
        /// Gets/Sets whether the grid allow paging.
        /// </summary>
        public bool Pagable { get; set; }

        /// <summary>
        /// Gets/Sets the grid could be filterable.
        /// </summary>
        public bool Filterable { get; set; }

        /// <summary>
        /// Gets/Sets whether the Grid allows sort columns.
        /// </summary>
        public bool Sortable { get; set; }

        /// <summary>
        /// Gets/Sets whether the Grid allows group columns
        /// </summary>
        public bool Groupable { get; set; }

        /// <summary>
        /// Gets/Sets the footer template of the Grid
        /// </summary>
        public HtmlTemplate Footer
        {
            get { return footer; }
            set { footer = value; }
        }

        /// <summary>
        /// Gets/Sets the header template of the Grid.
        /// </summary>
        public HtmlTemplate Header
        {
            get { return header; }
            set { header = value; }
        }

        /// <summary>
        /// Gets/Sets the DetailView template of data rows.
        /// </summary>
        public HtmlTemplate<object> DetailView
        {
            get { return detailView; }
            set { detailView = value; }
        }

        /// <summary>
        /// Gets/Sets the column collection that added to this grid.
        /// </summary>
        public virtual IList<GridColumn> Columns
        {
            get { return InnerItems; }
            set { InnerItems = value; }
        }

        /// <summary>
        /// Gets / Sets the grouping columns
        /// </summary>
        public virtual List<GridColumnRef> GroupColumns
        {
            get
            {
                if (groupColumns == null)
                    groupColumns = new List<GridColumnRef>();
                return groupColumns;
            }
        }

        /// <summary>
        /// Gets/Sets the ordering columns.
        /// </summary>
        public virtual List<GridOrderedColumnRef> OrderedColumns
        {
            get
            {
                if (orderColumns == null)
                    orderColumns = new List<GridOrderedColumnRef>();
                return orderColumns;
            }
        }

        protected override void OnItemAdded(GridColumn item)
        {
            base.OnItemAdded(item);
            if (string.IsNullOrEmpty(item.Name))
                item.Name = this.Name + "_column" + InnerItems.Count.ToString();
            item.Grid = this;
        }

        public virtual Action<object> RowTemplate { get; set; }

        public IEnumerable Model { get; set; }

        public override string TagName
        {
            get
            {
                return "div";
            }
        }

        public override void RenderBeginTag(HtmlTextWriter writer)
        {
            //this.MergeAttribute("class", "d-grid");
            //if (Height > 0)+ (ShowHorizontalScrollBar ? " d-grid-horizontal-scroll" : "")
            //    this.MergeAttribute("style", "height:"+Height.ToString()+"px");
            base.RenderBeginTag(writer);
        }

        public override void RenderContent(HtmlTextWriter writer)
        {
            this.RenderGridHeader(writer);
            this.RenderGroupArea(writer);
            this.RenderGridBody(writer);
            this.RenderGridFooter(writer);
        }

        /// <summary>
        /// Write the group area html elements to output.
        /// </summary>
        /// <param name="writer"></param>
        private void RenderGroupArea(HtmlTextWriter writer)
        {
            if (Groupable)
            {
                writer.WriteBeginTag("ul");
                writer.WriteAttribute("class", "d-grid-groups");

                writer.Write(HtmlTextWriter.TagRightChar);
                if (GroupColumns.Count == 0)
                    writer.Write("<li class=\"d-grid-group-empty\">Drag a column header and drop it here to group by that column</li>");
                else
                {
                    writer.Write("<li class=\"d-grid-group-empty\" style=\"display:none;\">Drag a column header and drop it here to group by that column</li>");
                    foreach (var colRef in GroupColumns)
                    {
                        var col = colRef.Column;

                        writer.WriteBeginTag("li");
                        writer.Write(HtmlTextWriter.TagRightChar);
                        var column = new TagBuilder("a");
                        column.MergeAttribute("href", "javascript:void(0);");

                        if (col.SortingOrder == SortingOrders.Desc)
                            column.AddCssClass("d-grid-column-desc");
                        else
                            column.AddCssClass("d-grid-column-asc");

                        var hidden = new TagBuilder("input");
                        hidden.MergeAttribute("type", "hidden");
                        hidden.MergeAttribute("value", col.DataField);

                        string closeIcon = "<span class=\"d-grid-group-remove-icon\"></span>";
                        column.InnerHtml = closeIcon + col.Title + "<span class=\"d-grid-column-sorting-icon\"></span>";
                        writer.Write(column.ToString());
                        writer.Write(hidden.ToString(TagRenderMode.SelfClosing));

                        writer.WriteEndTag("li");
                    }
                }
                writer.WriteEndTag("ul");
            }
        }

        private void BuildDefaultRowTemplate(HtmlTextWriter writer)
        {
            RowTemplate = new Action<object>(data =>
            {
                foreach (var column in Columns)
                {
                    writer.WriteBeginTag("td");
                    writer.WriteAttribute("class", "d-grid-cell " + column.Id.ToString());
                    writer.WriteAttribute("scope", "col");
                    if (column.TextAlign != System.Web.UI.WebControls.HorizontalAlign.Left)
                        writer.WriteAttribute("align", column.TextAlign.ToString().ToLower());

                    //if (column.Width > 0)
                    //writer.WriteAttribute("style", "width:" + column.Width.ToString() + "px;");
                    writer.Write(HtmlTextWriter.TagRightChar);
                    column.RenderCell(writer, data);
                    writer.WriteEndTag("td");
                }
            });
        }

        private void RenderGridHeader(HtmlTextWriter writer)
        {
            writer.WriteBeginTag("div");
            writer.WriteAttribute("class", "d-grid-header");
            writer.Write(HtmlTextWriter.TagRightChar);
            if (!Header.IsEmpty)
                Header.WriteTo(writer);
            writer.WriteEndTag("div");
        }
        
        private int GetColumnsWidth()
        {
            int width=0;
            foreach (var col in Columns)
            {
                if (col.Width == 0)
                    width += 100;
                else
                    width += col.Width;
            }
            return width;
        }

        private void RenderGridBody(HtmlTextWriter writer)
        {
            if (ShowScrollBar)
            {
                writer.WriteBeginTag("div");
                writer.WriteAttribute("class", "d-grid-columns");
                if (this.Width > 0)
                    writer.WriteAttribute("style", "width:" + (Width - 17).ToString() + "px;margin-right:17px;");
                writer.Write(HtmlTextWriter.TagRightChar);

                //Render column area
                writer.WriteBeginTag("table");
                writer.WriteAttribute("cellSpacing", "0");
                var colsWith = GetColumnsWidth();
                if (this.Width > 0)
                {
                    if (colsWith > this.Width)
                        writer.WriteAttribute("style", "width:" + colsWith.ToString() + "px");
                    else
                        writer.WriteAttribute("style", "min-width:" + (Width - 17).ToString() + "px");
                }
                        
                writer.Write(HtmlTextWriter.TagRightChar);
                RenderColGroup(writer);
                RenderColumns(writer);
                writer.WriteEndTag("table");
                writer.WriteEndTag("div");

                //Render data area
                writer.WriteBeginTag("div");
                writer.WriteAttribute("class", "d-grid-data-area");
                if (this.Width > 0)
                    writer.WriteAttribute("style", "width:" + Width.ToString() + "px;overflow:scroll;");
                writer.Write(HtmlTextWriter.TagRightChar);

                //Build Row template
                if (RowTemplate == null)
                    BuildDefaultRowTemplate(writer);

                writer.WriteBeginTag("table");
                writer.WriteAttribute("class", "d-grid-data-table");
                if (this.Width > 0)
                {
                    if (colsWith > this.Width)
                        writer.WriteAttribute("style", "width:" + colsWith.ToString() + "px");
                    else
                        writer.WriteAttribute("style", "min-width:" + (Width - 17).ToString() + "px");
                }
                writer.WriteAttribute("cellSpacing", "0");
                writer.Write(HtmlTextWriter.TagRightChar);
                RenderColGroup(writer);
                writer.WriteFullBeginTag("tbody");
                RenderDataRows(writer);
                writer.WriteEndTag("tbody");
                writer.WriteEndTag("table");

                //End data area
                writer.WriteEndTag("div");

            }
            else
            {
                writer.WriteBeginTag("table");
                writer.WriteAttribute("cellspacing", "0");
                writer.Write(HtmlTextWriter.TagRightChar);
                RenderColGroup(writer);

                //Render column area
                writer.WriteBeginTag("thead");
                writer.WriteAttribute("class", "d-grid-columns");
                //if (this.Width > 0)
                //    writer.WriteAttribute("style", "width:" + (Width - 17).ToString() + "px;margin-right:17px;");
                writer.Write(HtmlTextWriter.TagRightChar);


                //writer.WriteBeginTag("table");
                //writer.WriteAttribute("cellSpacing", "0");
                //writer.Write(HtmlTextWriter.TagRightChar);

                RenderColumns(writer);
                writer.WriteEndTag("thead");
                //writer.WriteEndTag("div");

                //Render data area
                writer.WriteBeginTag("tbody");
                writer.WriteAttribute("class", "d-grid-data-area");
                //if (this.Width > 0)
                //    writer.WriteAttribute("style", "width:" + Width.ToString() + "px;overflow:scroll;");
                writer.Write(HtmlTextWriter.TagRightChar);

                //Build Row template
                if (RowTemplate == null)
                    BuildDefaultRowTemplate(writer);

                //writer.WriteBeginTag("table");
                //writer.WriteAttribute("class", "d-grid-data-table");
                //writer.WriteAttribute("cellSpacing", "0");
                //writer.Write(HtmlTextWriter.TagRightChar);
                RenderDataRows(writer);
                writer.WriteEndTag("tbody");

                //End data area
                writer.WriteEndTag("table");
            }
        }


        private void RenderColumns(HtmlTextWriter writer)
        {
            writer.WriteFullBeginTag("tr");
            for (int i = 0; i < GroupColumns.Count; i++)
            {
                writer.WriteBeginTag("th");
                writer.WriteAttribute("class", "d-grid-column");
                //writer.WriteAttribute("style", "width:20px");
                writer.Write(HtmlTextWriter.TagRightChar);
                writer.WriteEndTag("th");
            }

            if ((!DetailView.IsEmpty) || (ClientDetailView != null))
            {
                writer.WriteBeginTag("th");
                writer.WriteAttribute("class", "d-grid-column");
                //writer.WriteAttribute("style", "width:20px");
                writer.Write(HtmlTextWriter.TagRightChar);
                writer.WriteEndTag("th");
            }

            foreach (var col in Columns)
            {
                if (OrderedColumns.Count > 0)
                {
                    var colRef = OrderedColumns.FirstOrDefault(c => c.Name.Equals(col.DataField, StringComparison.OrdinalIgnoreCase));
                    if (colRef != null)
                        col.SortingOrder = colRef.Order;
                }

                col.Render(writer);
            }

            writer.WriteEndTag("tr");

            if (Filterable)
            {
                writer.WriteFullBeginTag("tr");
                for (int i = 0; i < GroupColumns.Count; i++)
                {
                    writer.WriteBeginTag("td");
                    writer.WriteAttribute("class", "d-grid-column");
                    writer.WriteAttribute("style", "width:20px");
                    writer.Write(HtmlTextWriter.TagRightChar);
                    writer.WriteEndTag("td");
                }

                foreach (var col in Columns)
                    col.RenderFilter(writer);

                writer.WriteEndTag("tr");
            }
        }

        private void RenderColGroup(HtmlTextWriter writer)
        {
            writer.WriteFullBeginTag("colgroup");

            for (int i = 0; i < GroupColumns.Count; i++)
            {
                writer.WriteBeginTag("col");
                //writer.WriteAttribute("class", "d-grid-column");
                writer.WriteAttribute("style", "width:20px");
                writer.Write(HtmlTextWriter.TagRightChar);
                writer.WriteEndTag("col");
            }

            if ((!DetailView.IsEmpty) || (ClientDetailView != null))
            {
                writer.WriteBeginTag("col");
                //writer.WriteAttribute("class", "d-grid-column");
                writer.WriteAttribute("style", "width:20px");
                writer.Write(HtmlTextWriter.TagRightChar);
                writer.WriteEndTag("col");
            }

            foreach (var col in Columns)
            {
                writer.WriteBeginTag("col");
                //writer.WriteAttribute("class", "d-grid-column");
                if ((col.Width == 0) && (ShowScrollBar)) col.Width = 100;

                if (col.Width > 0)
                    writer.WriteAttribute("style", "width:" + col.Width.ToString() + "px");
                writer.Write(HtmlTextWriter.TagRightChar);
                writer.WriteEndTag("col");
            }

            writer.WriteEndTag("colgroup");
        }

        private void RenderDataRows(HtmlTextWriter writer)
        {
            if (Model != null)
            {
                var groupingModel = Model as IEnumerable<DynamicGroupResult>;

                if (groupingModel != null)
                    RenderGroups(writer, groupingModel);
                else
                    RenderRows(writer, Model);
            }
        }

        private int groupSeq = -1;

        private void RenderGroups(HtmlTextWriter writer, IEnumerable<DynamicGroupResult> groupingModel)
        {
            groupSeq++;
            var groupColumn = groupSeq < GroupColumns.Count ? GroupColumns[groupSeq] : null;

            if (groupColumn != null)
            {
                foreach (var group in groupingModel)
                {
                    writer.WriteBeginTag("tr");
                    writer.WriteAttribute("class", "d-grid-grouping-row");
                    if (groupSeq > -1)
                        writer.WriteAttribute("level", groupSeq.ToString());
                    writer.Write(HtmlTextWriter.TagRightChar);

                    RenderGroupCells(writer);

                    writer.WriteBeginTag("td");
                    writer.WriteAttribute("colspan", (Columns.Count - (groupSeq - 2)).ToString());
                    writer.WriteAttribute("class", "d-grid-cell d-grid-grouping-cell");
                    writer.Write(HtmlTextWriter.TagRightChar);

                    writer.WriteBeginTag("a");
                    writer.WriteAttribute("class", "d-grid-group-holder");
                    writer.WriteAttribute("href", "javascript:void(0);");
                    writer.Write(HtmlTextWriter.TagRightChar);

                    writer.WriteBeginTag("span");
                    writer.WriteAttribute("class", "d-grid-group-button");
                    writer.Write(HtmlTextWriter.TagRightChar);
                    writer.WriteEndTag("span");

                    writer.Write(groupColumn.Column.Title);
                    writer.Write(":");
                    writer.Write(group.Key);
                    writer.Write(" (" + group.Count + ")");
                    writer.WriteEndTag("a");

                    writer.WriteEndTag("td");
                    writer.WriteEndTag("tr");

                    if (group.SubGroups != null)
                    {
                        RenderGroups(writer, group.SubGroups);
                        groupSeq--;
                    }
                    else
                    {
                        groupSeq++;
                        RenderRows(writer, group.Items);
                        groupSeq--;
                    }
                }
            }
        }

        private void RenderGroupCells(HtmlTextWriter writer)
        {
            var high = groupSeq;
            for (int i = 0; i < high; i++)
            {
                writer.WriteBeginTag("td");
                writer.WriteAttribute("class", "d-grid-cell d-grid-grouping-cell");
                writer.Write(HtmlTextWriter.TagRightChar);
                writer.WriteEndTag("td");
            }
        }

        private void RenderRows(HtmlTextWriter writer, IEnumerable dataModel)
        {
            var enumerator = dataModel.GetEnumerator();
            int i = 0;
            while (enumerator.MoveNext())
            {
                var dataItem = enumerator.Current;
                writer.WriteBeginTag("tr");
                if (this.groupSeq > -1)
                    writer.WriteAttribute("level", groupSeq.ToString());
                if ((i % 2) != 0)
                    writer.WriteAttribute("class", "d-grid-row d-grid-row-alt");
                else
                    writer.WriteAttribute("class", "d-grid-row");
                writer.Write(HtmlTextWriter.TagRightChar);
                RenderGroupCells(writer);

                if (!DetailView.IsEmpty)
                {
                    writer.WriteBeginTag("td");
                    writer.WriteAttribute("class", "d-grid-cell");
                    writer.Write(HtmlTextWriter.TagRightChar);
                    writer.WriteBeginTag("span");
                    writer.WriteAttribute("class", "d-grid-detail-button");
                    writer.Write(HtmlTextWriter.TagRightChar);
                    writer.WriteEndTag("span");
                    writer.WriteEndTag("td");
                }

                RowTemplate.Invoke(dataItem);
                writer.WriteEndTag("tr");

                if (!DetailView.IsEmpty)
                {
                    writer.WriteBeginTag("tr");
                    writer.WriteAttribute("class", "d-grid-row-detail");
                    writer.Write(HtmlTextWriter.TagRightChar);

                    writer.WriteBeginTag("td");
                    writer.WriteAttribute("style", "width:20px;");
                    writer.Write(HtmlTextWriter.TagRightChar);
                    writer.WriteEndTag("td");

                    RenderGroupCells(writer);

                    writer.WriteBeginTag("td");
                    writer.WriteAttribute("class", "d-grid-detail");
                    writer.WriteAttribute("colspan", Columns.Count.ToString());
                    writer.Write(HtmlTextWriter.TagRightChar);
                    DetailView.WriteTo(dataItem, writer);
                    writer.WriteEndTag("td");

                    writer.WriteEndTag("tr");
                }
                i++;
            }
        }

        private void RenderGridFooter(HtmlTextWriter writer)
        {
            writer.WriteBeginTag("div");
            writer.WriteAttribute("class", "d-grid-footer");
            writer.Write(HtmlTextWriter.TagRightChar);
            if (!Footer.IsEmpty)
                Footer.WriteTo(writer);
            writer.WriteEndTag("div");
        }
    }
}
